﻿/* EntitiesToDTOs. Copyright (c) 2011. Fabian Fernandez.
 * http://entitiestodtos.codeplex.com
 * Licensed by Common Development and Distribution License (CDDL).
 * http://entitiestodtos.codeplex.com/license
 * Fabian Fernandez. 
 * http://www.linkedin.com/in/fabianfernandezb/en
 * */
using EnvDTE;
using EnvDTE80;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using EntitiesToDTOs.Properties;
using EntitiesToDTOs.Helpers;
using EntitiesToDTOs.Generators.Parameters;
using EntitiesToDTOs.Enums;

namespace EntitiesToDTOs.Domain
{
    /// <summary>
    /// Represents a DTO to generate.
    /// </summary>
    internal class EntityDTO
    {
        /// <summary>
        /// The name found in the EDMX for this Entity Type.
        /// </summary>
        public string EntityName { get; private set; }

        /// <summary>
        /// DTO Class name.
        /// </summary>
        public string DTOName { get; private set; }

        /// <summary>
        /// DTO Base Class name.
        /// </summary>
        public string DTOBaseName { get; private set; }

        /// <summary>
        /// DTO Base Class reference.
        /// </summary>
        public EntityDTO DTOBase { get; private set; }

        /// <summary>
        /// DTOs childs of this DTO.
        /// </summary>
        public List<EntityDTO> DTOChilds { get; private set; }

        /// <summary>
        /// DTO Class access.
        /// </summary>
        public vsCMAccess ClassAccess { get; private set; }

        /// <summary>
        /// DTO Class Kind (used to generate partial classes).
        /// </summary>
        public vsCMClassKind ClassKind { get; private set; }

        /// <summary>
        /// Indicates if this DTO is Abstract.
        /// </summary>
        public bool IsAbstract { get; private set; }

        /// <summary>
        /// Class Attributes of this DTO.
        /// </summary>
        public List<EntityDTOAttribute> ClassAttributes { get; private set; }

        /// <summary>
        /// Properties of this DTO.
        /// </summary>
        public List<EntityDTOProperty> Properties { get; private set; }

        /// <summary>
        /// The Assembler to generate for this DTO.
        /// </summary>
        public Assembler Assembler { get; private set; }



        /// <summary>
        /// Creates an instance of <see cref="EntityDTO"/>.
        /// </summary>
        /// <param name="genParams">Parameters for the generation of DTOs.</param>
        public EntityDTO(XElement entityNode, List<EntityNavigation> navigations, GenerateDTOsParams genParams)
        {
            // Set Entity name
            this.EntityName = entityNode.Attribute(EdmxNodeAttributes.EntityType_Name).Value;

            // Set DTO name
            this.DTOName = Utils.ConstructDTOName(this.EntityName, genParams);

            // Define Class Base Type (if exists)
            string entityBaseType = EdmxHelper.GetEntityBaseType(entityNode);
            if (string.IsNullOrWhiteSpace(entityBaseType) == false)
            {
                // Check if base type is going to be generated
                if (genParams.TypesToGenerateFilter.Contains(entityBaseType) == true)
                {
                    this.DTOBaseName = Utils.ConstructDTOName(entityBaseType, genParams);
                }
            }

            // Set Class Access
            this.ClassAccess = vsCMAccess.vsCMAccessPublic;

            // Set the Class Kind to partial so the final user can extend it
            this.ClassKind = vsCMClassKind.vsCMClassKindPartialClass;

            #region Set IsAbstract
            if (entityNode.Attribute(EdmxNodeAttributes.EntityType_Abstract) != null)
            {
                string abstractValue = entityNode.Attribute(EdmxNodeAttributes.EntityType_Abstract).Value.ToLower();
                this.IsAbstract = (abstractValue == Resources.XmlBoolTrue);
            }
            #endregion Set IsAbstract

            #region Set Class Attributes
            this.ClassAttributes = new List<EntityDTOAttribute>();

            if (genParams.DTOsServiceReady)
            {
                this.ClassAttributes.Add(new EntityDTOAttribute(Resources.AttributeDataContract, null));
            }
            #endregion Set Class Attributes

            #region Set Properties
            this.Properties = new List<EntityDTOProperty>();

            // Get Key Node
            XElement entityKeyNode = entityNode.Descendants(EdmxNodes.Key).FirstOrDefault();

            // Get the Entity Keys
            var entityKeys = new List<string>();
            if(entityKeyNode != null)
            {
                entityKeys = entityKeyNode.Descendants(EdmxNodes.PropertyRef)
                    .Select(n => n.Attribute(EdmxNodeAttributes.PropertyRef_Name).Value).ToList();
            }

            // Get Property Nodes
            IEnumerable<XElement> entityPropertiesNodes = entityNode.Descendants(EdmxNodes.Property);

            // Variables
            string typeValue;
            string[] typeValueSplitted;
            string edmxTypeName;
            bool addProperty;

            foreach (XElement propertyNode in entityPropertiesNodes)
            {
                // Indicates if the property must be added
                addProperty = true;

                // Get the Type value
                typeValue = propertyNode.Attribute(EdmxNodeAttributes.Property_Type).Value;

                // Split typeValue to check if it is a Complex Type
                typeValueSplitted = typeValue.Split(new string[] { Resources.Dot }, StringSplitOptions.RemoveEmptyEntries);

                // Get the Complex Type EDMX name (if it is one)
                edmxTypeName = typeValueSplitted[(typeValueSplitted.Length - 1)];

                // Check if property type is complex or entity type
                if (genParams.AllComplexTypesNames.Contains(edmxTypeName) == true
                    || genParams.AllEntityTypesNames.Contains(edmxTypeName) == true)
                {
                    // Check if the type is going to be generated
                    addProperty = genParams.TypesToGenerateFilter.Contains(edmxTypeName);
                }

                if (addProperty == true)
                {
                    // Check if it is a complex type property
                    bool isComplex = (genParams.AllComplexTypesNames.Contains(edmxTypeName) == true);

                    this.Properties.Add(new EntityDTOProperty(propertyNode, entityKeys, 
                        this.EntityName, genParams.DTOsServiceReady, isComplex));
                }
            }
            #endregion Set Properties

            #region Set Navigation Properties
            if (navigations != null)
            {
                IEnumerable<EntityNavigation> myNavigations = navigations.Where(a => a.DTOName == this.DTOName);

                foreach (EntityNavigation entityNav in myNavigations)
                {
                    foreach (EntityNavigationProperty navProperty in entityNav.NavigationProperties)
                    {
                        // Add property only if target type is going to be generated
                        if (genParams.TypesToGenerateFilter.Contains(navProperty.EntityTargetName) == true)
                        {
                            this.Properties.Add(new EntityDTOProperty(
                                entityNav.NavigationPropertyNameEDMX, navProperty, genParams.DTOsServiceReady));
                        }
                    }
                }
            }
            #endregion Set Association Properties

            // Set Assembler (it does not have one by default)
            this.Assembler = null;
        }



        /// <summary>
        /// Sets the KnowTypes of this DTO.
        /// </summary>
        /// <param name="entities">List of all the DTOs that are going to be generated.</param>
        /// <param name="isServiceReady">Specifies if the DTOs are Service-Ready.</param>
        public void SetKnownTypes(List<EntityDTO> entities, bool isServiceReady)
        {
            if (isServiceReady)
            {
                IEnumerable<EntityDTO> knownTypes = entities.Where(e => e.DTOBaseName == this.DTOName);

                foreach (EntityDTO entity in knownTypes)
                {
                    this.ClassAttributes.Add(new EntityDTOAttribute(Resources.AttributeKnownType,
                            string.Format(Resources.CSharpCodeTypeOf, entity.DTOName)));
                }
            }
        }

        /// <summary>
        /// Sets the childs of this DTO.
        /// </summary>
        /// <param name="entities">List of all the DTOs that are going to be generated.</param>
        public void SetChilds(List<EntityDTO> entities)
        {
            this.DTOChilds = entities.Where(e => e.DTOBaseName == this.DTOName).ToList();
        }

        /// <summary>
        /// Sets the reference to the DTO Base Class.
        /// </summary>
        /// <param name="entitiesDTOs">Entities DTOs to generate.</param>
        public void SetDTOBase(List<EntityDTO> entitiesDTOs)
        {
            if (string.IsNullOrWhiteSpace(this.DTOBaseName) == false)
            {
                EntityDTO baseDTO = entitiesDTOs.FirstOrDefault(e => e.DTOName == this.DTOBaseName);

                this.DTOBase = baseDTO;
            }
        }

        /// <summary>
        /// Sets the Assembler for this DTO.
        /// </summary>
        /// <param name="genParams">Assemblers generation parameters.</param>
        public void SetAssembler(GenerateAssemblersParams genParams)
        {
            this.Assembler = new Assembler(this, 
                Utils.ConstructAssemblerName(this.EntityName, genParams));
        }

    }
}